//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using OpenLS.Chart.Internals;

namespace OpenLS.Chart.UI.Internals
{
    ///<summary>
    /// Arranges child elements in 2D bars that can be horizonal or vertical, clustered, stacked or percent stacked
    ///</summary>
    public sealed class BarPanel : Panel
    {
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (Children.Count == 0)
                return finalSize;

            switch (Grouping)
            {
                case BarGrouping.Standard:
                case BarGrouping.Clustered:
                    return arrangeClustered(finalSize);
                case BarGrouping.Stacked:
                    return arrangeStacked(finalSize);
                case BarGrouping.PercentStacked:
                    return arrangePercentStacked(finalSize);
                default:
                    throw new NotSupportedException();
            }
        }

/*
        internal const double percentFactor = 1;
*/
        private Size arrangePercentStacked(Size finalSize)
        {
            var chart = ChartElement.GetChart(this);
            if (chart == null)
            {
                Debug.WriteLine("problem..., no chart");
                return finalSize;
            }
            ChartLayout info = ChartToChartInfoConverter.GetInfo(chart);
            AxisOrientation valueAxisOrientation = ValueAxisPanel.GetValueAxisOrientation(this);
            AxisOrientation categoryAxisOrientation = CategoryAxisPanel.GetCategoryAxisOrientation(this);
            const bool usesMargins = true;
            if (Orientation == Orientation.Vertical)
            {
                double itemHeight = finalSize.Height / info.Count;
                double actualItemHeight = itemHeight * 100 / (Gap + 100);
                double gapHeight = itemHeight * Gap / (Gap + 100);
                foreach (FrameworkElement element in Children)
                {
                    Bounds bounds = info.GetBounds(SeriesElement.GetIndex(element), SeriesElement.GetSeries(element).Index);
                    double currentY = CategoryAxisPanel.GetScreenPosition(SeriesElement.GetIndex(element), 0, info.Count - 1, finalSize.Height, 0, categoryAxisOrientation, usesMargins)
                                      - itemHeight / 2 + gapHeight / 2;
                    Bounds screenBounds =
                        getBounds(bounds.Low, bounds.High, Minimum, Maximum, 0, finalSize.Width, valueAxisOrientation);
                    element.Arrange(new Rect(
                                        new Point(screenBounds.Low, currentY),
                                        new Size(screenBounds.Delta, actualItemHeight)));
                }
            }

            else
            {

                double itemWidth = finalSize.Width / info.Count;
                double actualItemWidth = itemWidth * 100 / (Gap + 100);
                double gapWidth = itemWidth * Gap / (Gap + 100);
                foreach (FrameworkElement element in Children)
                {
                    Bounds bounds = info.GetBounds(SeriesElement.GetIndex(element), SeriesElement.GetSeries(element).Index);
                    //\\currentX = gapWidth / 2 + itemWidth * GetIndex(element);
                    double currentX = CategoryAxisPanel.GetScreenPosition(SeriesElement.GetIndex(element), 0, info.Count - 1, 0, finalSize.Width, categoryAxisOrientation, usesMargins) -
                                      itemWidth / 2 + gapWidth / 2;
                    Bounds screenBounds =
                        getBounds(bounds.Low , bounds.High , Minimum, Maximum, finalSize.Height, 0, valueAxisOrientation);
                    element.Arrange(new Rect(
                                        new Point(currentX, screenBounds.Low),
                                        new Size(actualItemWidth, screenBounds.Delta)));
                }
            }
            return finalSize;
        }

        private Size arrangeStacked(Size finalSize)
        {
            var chart = ChartElement.GetChart(this);
            if (chart == null)
            {
                Debug.WriteLine("problem..., no chart");
                return finalSize;
            }
            ChartLayout info = ChartToChartInfoConverter.GetInfo(chart);
            AxisOrientation valueAxisOrientation = ValueAxisPanel.GetValueAxisOrientation(this);
            AxisOrientation categoryAxisOrientation = CategoryAxisPanel.GetCategoryAxisOrientation(this);
            const bool usesMargins = true;
            var min = Minimum;
            if (Orientation == Orientation.Vertical)
            {
                double itemHeight = finalSize.Height / info.Count;
                double actualItemHeight = itemHeight * 100 / (Gap + 100);
                double gapHeight= itemHeight* Gap / (Gap + 100);
                foreach (FrameworkElement element in Children)
                {
                    Bounds bounds = info.GetBounds(SeriesElement.GetIndex(element), SeriesElement.GetSeries(element).Index);
                    //\\currentY = (gapHeight / 2 + itemHeight * GetIndex(element));
                    double currentY = CategoryAxisPanel.GetScreenPosition(SeriesElement.GetIndex(element), 0, info.Count - 1, finalSize.Height, 0, categoryAxisOrientation, usesMargins)
                                      - itemHeight / 2 + gapHeight / 2;
                    Bounds screenBounds = 
                        getBounds(bounds.Low, bounds.High, min, Maximum, 0, finalSize.Width, valueAxisOrientation);
                    element.Arrange(new Rect(
                                        new Point(screenBounds.Low, currentY),
                                        new Size(screenBounds.Delta, actualItemHeight)));
                }
            }

            else
            {

                double itemWidth = finalSize.Width / info.Count;
                double actualItemWidth = itemWidth * 100 / (Gap + 100);
                double gapWidth = itemWidth * Gap / (Gap + 100);
                foreach (FrameworkElement element in Children)
                {
                   // (NumericValue)element.DataContext;
                    Bounds bounds = info.GetBounds(SeriesElement.GetIndex(element), SeriesElement.GetSeries(element).Index);
                    //\\currentX = gapWidth / 2 + itemWidth * GetIndex(element);
                    double currentX = CategoryAxisPanel.GetScreenPosition(SeriesElement.GetIndex(element), 0, info.Count - 1, 0, finalSize.Width, categoryAxisOrientation, usesMargins) - 
                                      itemWidth / 2 + gapWidth / 2;
                    Bounds screenBounds =
                        getBounds(bounds.Low, bounds.High, min, Maximum, finalSize.Height, 0, valueAxisOrientation);
                    element.Arrange(new Rect(
                                        new Point(currentX  , screenBounds.Low),
                                        new Size(actualItemWidth, screenBounds.Delta)));
                }
            }
            return finalSize;
        }

        private Size arrangeClustered(Size finalSize)
        {
            double diff = Maximum - Minimum;
            if (diff <= 0)
                diff = 1.0;
            var chart = (BarChartBase)ChartElement.GetChart(this);
            var info = new ClusteredDisplayInformation(chart.Series);
            const bool usesMargins = true; //\\ CategoryAxisPanel.GetUsesMargirs(this);
            AxisOrientation categoryAxisOrientation = CategoryAxisPanel.GetCategoryAxisOrientation(this);
            AxisOrientation valueAxisOrientation = ValueAxisPanel.GetValueAxisOrientation(this);
            double zero;
            if (Minimum > 0)
                zero = Minimum;
            else if (Maximum < 0)
                zero = Maximum;
            else
            {
                zero = 0;
            }
            if (Orientation == Orientation.Vertical)
            {
                double itemHeight = finalSize.Height / info.Count;
                double actualItemHeight = itemHeight * 100 / (Gap + 100);
                double gapHeight = itemHeight * Gap / (Gap + 100);
                foreach (FrameworkElement element in Children)
                {
                    //NumericValue v = (NumericValue)element.DataContext;
                    //\\currentY = (gapHeight / 2 + GetIndex(element) * itemHeight);
                    double currentY = CategoryAxisPanel.GetScreenPosition(SeriesElement.GetIndex(element), 0, info.Count - 1, finalSize.Height, 0,
                                                                          categoryAxisOrientation, usesMargins) - itemHeight / 2  + gapHeight / 2;
                    Bounds screenBounds = getBounds(SeriesElement.GetValue(element), zero, Minimum, Maximum, 0, finalSize.Width, valueAxisOrientation);

                    element.Arrange(new Rect(new Point(screenBounds.Low, currentY - (actualItemHeight / SeriesCount * SeriesElement.GetSeries(element).Index)),
                                             new Size(screenBounds.Delta, actualItemHeight / SeriesCount)));
                }
            }
            else
            {
                double itemWidth = finalSize.Width / info.Count;
                double actualItemWidth = itemWidth * 100 / (Gap + 100);
                double gapWidth = itemWidth * Gap / (Gap + 100);
                foreach (FrameworkElement element in Children)
                {
                    //NumericValue v = (NumericValue)element.DataContext;
                    //\\ currentX = gapWidth / 2 + GetIndex(element) * itemWidth;
                    double currentX = CategoryAxisPanel.GetScreenPosition(SeriesElement.GetIndex(element), 0, info.Count - 1, 0, finalSize.Width, categoryAxisOrientation, usesMargins) 
                                      - itemWidth / 2 + gapWidth / 2;
                    Bounds screenBounds = getBounds(SeriesElement.GetValue(element), zero, Minimum, Maximum, finalSize.Height, 0, valueAxisOrientation);
                    element.Arrange(new Rect(
                                        new Point(currentX + actualItemWidth / SeriesCount * SeriesElement.GetSeries(element).Index, screenBounds.Low),
                                        new Size(actualItemWidth / SeriesCount, screenBounds.Delta)));
                }
            }
            return finalSize;
        }
        static Bounds getBounds(double value, double value2, double minValue, double maxValue, double screenMin, double screenMax, AxisOrientation orientation)
        {
            if (orientation == AxisOrientation.MaximumToMinimum)
                return getBounds(value, value2, minValue, maxValue, screenMax, screenMin, AxisOrientation.MinimumToMaximum);
           // minValue = Math.Min(minValue, 0);
           // maxValue = Math.Max(maxValue, 0);
            if (minValue == maxValue)
            {
                return Bounds.Create(screenMin, screenMin);
            }
            double diff = maxValue - minValue;
            double size = screenMax - screenMin;
            if (size == 0 || diff <= 0)
            {
                return Bounds.Create(screenMin, screenMin);
            }
            double vv = (value - minValue) / diff * size + screenMin;
            double v0 = (value2 - minValue) / diff * size + screenMin;
            return Bounds.Create(v0, vv);
        }


        ///<summary>
        /// Get or Set the type of grouping
        ///</summary>
        public BarGrouping Grouping
        {
            get { return (BarGrouping)GetValue(GroupingProperty); }
            set { SetValue(GroupingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Grouping.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GroupingProperty =
            DependencyProperty.Register("Grouping", typeof(BarGrouping), typeof(BarPanel), new UIPropertyMetadata(BarGrouping.Standard));


        protected override Size MeasureOverride(Size constraint)
        {
            var result = new Size();
            Size size2 = constraint;
            bool isHorizontal =  Orientation == Orientation.Horizontal;
            foreach (UIElement element1 in InternalChildren)
            {
                if (element1 == null)
                    continue;
                element1.Measure(size2);
                Size elemDesiredSize = element1.DesiredSize;
                if (isHorizontal)
                {
                    result.Width += elemDesiredSize.Width;
                    result.Height = Math.Max(result.Height, elemDesiredSize.Height);
                }
                else
                {
                    result.Width = Math.Max(result.Width, elemDesiredSize.Width);
                    result.Height += elemDesiredSize.Height;
                }
            }
            //\\ return constraint;
            return result;
        }
        ///<summary>
        /// Get or set the dimension by which child elements are arranged. This is a dependency property.
        ///</summary>
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Orientation.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(BarPanel),
                                        new FrameworkPropertyMetadata(Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsArrange));



        ///<summary>
        /// Get or set the gap between series. This is a dependency property.
        ///</summary>
        public double Gap
        {
            get { return (double)GetValue(GapProperty); }
            set { SetValue(GapProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GapProperty =
            DependencyProperty.Register("Gap", typeof(double), typeof(BarPanel), new FrameworkPropertyMetadata(00.0,
                                                                                                               FrameworkPropertyMetadataOptions.AffectsArrange));


        ///<summary>
        /// Get or set the number of series. This is a dependency property.
        ///</summary>
        public int SeriesCount
        {
            get { return (int)GetValue(SeriesCountProperty); }
            set { SetValue(SeriesCountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SeriesCount.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SeriesCountProperty =
            DependencyProperty.Register("SeriesCount", typeof(int), typeof(BarPanel), new UIPropertyMetadata(1));



        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Maximum.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaximumProperty =
            DependencyProperty.Register("Maximum", typeof(double), typeof(BarPanel), new FrameworkPropertyMetadata(0.0,
                                                                                                                   FrameworkPropertyMetadataOptions.AffectsArrange));




        ///<summary>
        /// Get or set the minimum value
        ///</summary>
        public double Minimum
        {
            get { return (double)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Minimum.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinimumProperty =
            DependencyProperty.Register("Minimum", typeof(double), typeof(BarPanel), new FrameworkPropertyMetadata(0.0,
                                                                                                                   FrameworkPropertyMetadataOptions.AffectsArrange));








    }
}